package org.stvd.common.security.filter;

import java.io.UnsupportedEncodingException;
import java.text.ParseException;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.log4j.MDC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.stvd.common.Consts;
import org.stvd.common.manager.AsyncManager;
import org.stvd.common.manager.factory.AsyncFactory;
import org.stvd.common.utils.encode.Base64Util;
import org.stvd.common.utils.encode.RSAUtil;
import org.stvd.core.util.DateUtil;
import org.stvd.core.util.StringUtil;
import org.stvd.core.util.cache.EHCacheUtil;
import org.stvd.core.util.json.JsonUtils;
import org.stvd.core.web.IPUtil;
import org.stvd.core.web.URLUtil;
import org.stvd.entities.admin.UserLogin;
import org.stvd.entities.admin.Users;
import org.stvd.service.admin.UsersService;

import com.alibaba.druid.util.StringUtils;

/*
 * MyUsernamePasswordAuthenticationFilter
    attemptAuthentication
        this.getAuthenticationManager()
            ProviderManager.java
                authenticate(UsernamePasswordAuthenticationToken authRequest)
                    AbstractUserDetailsAuthenticationProvider.java
                        authenticate(Authentication authentication)
                            P155 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
                                DaoAuthenticationProvider.java
                                    P86 loadUserByUsername
 */
public class UserAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    private Logger logger = LoggerFactory.getLogger(getClass());
    public static final String LOGIN_COOKIE_SEP = "[==]";
    public static final String USERNAME = "username";
    public static final String PASSWORD = "password";
    private boolean enableValidateCode = true;
    private String sessionvalidateCodeField = DEFAULT_SESSION_VALIDATE_CODE_FIELD;
    private final String validateCodeParameter = DEFAULT_VALIDATE_CODE_PARAMETER;
    public static final String DEFAULT_SESSION_VALIDATE_CODE_FIELD = "validateCode";
    public static final String DEFAULT_VALIDATE_CODE_PARAMETER = "validateCode";
    
    @Autowired
    private UsersService usersService;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        // 获取登录信息
        String username = obtainUsername(request).trim();
        String password = obtainPassword(request);
        boolean bValidate = true;
        String msg = "";// 错误信息
        
        if (StringUtil.isEmpty(username)) {
            bValidate = false;
            msg = "登录名不能为空！";
        } else if (StringUtil.isEmpty(password)) {
            bValidate = false;
            msg = "密码不能为空！";
        } else if (isEnableValidateCode()) {
            if(!checkValidateCode(request)){
                bValidate = false;
                msg = "验证码不正确！";
            }
            //清空验证码
            HttpSession session = request.getSession(false);
            if (session != null || getAllowSessionCreation()) {
                session.removeAttribute(validateCodeParameter);
            }
        }
        
        EHCacheUtil ehCacheUtil = new EHCacheUtil();
        //验证连续登录失败次数，超过5次，锁定10分钟
        if (bValidate) {
            ehCacheUtil.initCache("passwordRetryCache");
            Object locktime = ehCacheUtil.get(username+"_locktime"); 
            try {
                if(locktime == null || 
                        DateUtil.afterDate(DateUtil.getSystemDate(), 
                                DateUtil.getMinuteAdd(DateUtil.Y2DTIME_FORMAT.parse(locktime.toString()), 10))){
                    Object retryCount = ehCacheUtil.get(username);
                    if(retryCount != null){
                        retryCount = (int)retryCount+1;
                    } else {
                        retryCount = 1;
                        ehCacheUtil.remove(username+"_locktime");
                    }
                    if((int)retryCount > 5){
                        bValidate = false;
                        ehCacheUtil.put(username+"_locktime",DateUtil.getSystemDateOfString());
                        msg = "登录失败连续超过5次，请10分钟后再试！";
                        AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
                    } else {
                        ehCacheUtil.put(username, retryCount);
                    }
                } else {
                    bValidate = false;
                    msg = "登录失败连续超过5次，请10分钟后再试！";
                    AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
                }
            } catch (ParseException e) {
                e.printStackTrace();
                AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, e.getMessage()));
            }
        }
        if(bValidate){
            //log4j日志记录登录信息
            MDC.put("ip", IPUtil.getIP(request));
            MDC.put("url", URLUtil.getURL(request));
            MDC.put("user", username);
            MDC.put("class", "org.stvd.common.security.filter.UserAuthenticationFilter");
            MDC.put("method", "attemptAuthentication");
            
            Map<String, String> map = new HashMap<String, String>();
            UserLogin userLogin = usersService.findUserLoginByLoginAccount(username);
            //判断账号是否存在
            if(userLogin !=null) {
                //判断账号登陆状态是否启用
                if(userLogin.getLoginStatus()!=null && "1".equals(userLogin.getLoginStatus())) {
                    String userId = userLogin.getUserId();
                    map.put("userId", userId);
                    
                    try {
                        // RSA解密
                        password = password.replaceAll("%2B","+");
                        String privateKey = RSAUtil.readTxt(this.getClass().getResource("/rsakey/privateKey.txt").getFile());
                        byte[] decryptByPrivateKey = RSAUtil.decryptByPrivateKey(Base64Util.decode(password), privateKey);
                        password = new String(decryptByPrivateKey);
                    
                        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userId, password);
                        setDetails(request, authRequest);
                        Authentication authentication = this.getAuthenticationManager().authenticate(authRequest);
                        if(authentication != null && authentication.isAuthenticated()){
                            
                            HttpSession session = request.getSession(false);
                            if (session != null || getAllowSessionCreation()) {
                                request.getSession().setAttribute("SPRING_SECURITY_SUCCESS_USERNAME", username);
                            }
                            //登录成功，则删除登录失败的cache记录
                            ehCacheUtil.remove(username);
                            ehCacheUtil.remove(username+"_locktime");
                            
                            msg = "用户登录成功！";
                            map.put("msg", msg);
                            logger.info(JsonUtils.coverToJson(map).toString());
                            AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_SUCCESS, msg));
                            
                            // 设置cookieStr的加密方式
                            if(Consts.AUTO_LOGIN) {
                                Cookie loginedCookie = null;
                                String cookieUserName = null;
                                String cookiePassword = null;
                                try {
                                    Users users = usersService.findByPk(Users.class, userId);
                                    if(userId!=null) {
                                        cookieUserName = Base64.getEncoder().encodeToString(userId.getBytes("utf-8"));
                                        cookiePassword = Base64.getEncoder().encodeToString(users.getPwd().getBytes("utf-8"));
                                    }
                                } catch (UnsupportedEncodingException e) {
                                    e.printStackTrace();
                                }
                                String cookieValue = null;
                                try {
                                    cookieValue = Base64.getEncoder().encodeToString(
                                            (cookieUserName + LOGIN_COOKIE_SEP + cookiePassword).getBytes("utf-8"));
                                } catch (UnsupportedEncodingException e) {
                                    e.printStackTrace();
                                }
                                if(loginedCookie==null){
                                    loginedCookie = new Cookie(Consts.COOKIE_KEY, cookieValue);
                                }
                                loginedCookie.setDomain(Consts.DOMAIN_NAME);
                                loginedCookie.setMaxAge(60 * 60 * 24 * 3); //有效期3天
                                loginedCookie.setPath("/");
                                response.addCookie(loginedCookie);
                            }
                            // 运行UserDetailsService的loadUserByUsername
                            // 再次封装Authentication
                            return authentication;
                        } else {
                            msg = "用户名或密码错误";
                            map.put("msg", msg);
                            logger.error(JsonUtils.coverToJson(map).toString());
                            AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
                        }
                    } catch (Exception e) {
                        msg = "用户名或密码错误";
                        map.put("msg", msg);
                        logger.error(JsonUtils.coverToJson(map).toString());
                        AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
                        e.printStackTrace();
                    }
                }else {
                    msg = "此登录账号已被禁用";
                    map.put("msg", msg);
                    logger.error(JsonUtils.coverToJson(map).toString());
                    AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
                }
            }else {
                msg = "登录用户名不存在";
                map.put("msg", msg);
                logger.error(JsonUtils.coverToJson(map).toString());
                AsyncManager.me().execute(AsyncFactory.recordLoginInfo(username, Consts.OPER_FAIL, msg));
            }
        }
        // 验证失败
        HttpSession session = request.getSession(false);

        if (session != null || getAllowSessionCreation()) {
            request.getSession().setAttribute("SPRING_SECURITY_LAST_USERNAME", username);
        }
        throw new AuthenticationServiceException(msg);
    }

    @Override
    protected String obtainUsername(HttpServletRequest request) {
        Object obj = request.getParameter(USERNAME);
        return null == obj ? "" : obj.toString();
    }

    @Override
    protected String obtainPassword(HttpServletRequest request) {
        Object obj = request.getParameter(PASSWORD);
        return null == obj ? "" : obj.toString();
    }

    /**
     * <li>比较session中的验证码和用户输入的验证码是否相等</li>
     */
    protected boolean checkValidateCode(HttpServletRequest request) {
        String sessionValidateCode = obtainSessionValidateCode(request);
        String validateCodeParameter = obtainValidateCodeParameter(request);
        
        return !StringUtils.isEmpty(validateCodeParameter) && sessionValidateCode.equalsIgnoreCase(validateCodeParameter);
    }

    private String obtainValidateCodeParameter(HttpServletRequest request) {
        return request.getParameter(validateCodeParameter);
    }

    protected String obtainSessionValidateCode(HttpServletRequest request) {
        Object obj = request.getSession().getAttribute(sessionvalidateCodeField);
        return null == obj ? "" : obj.toString();
    }

    public String getValidateCodeName() {
        return sessionvalidateCodeField;
    }

    public void setValidateCodeName(String validateCodeName) {
        this.sessionvalidateCodeField = validateCodeName;
    }

    public boolean isEnableValidateCode() {
        return enableValidateCode;
    }

    public void setEnableValidateCode(boolean enableValidateCode) {
        this.enableValidateCode = enableValidateCode;
    }
}
